<?php

/*****************************************************
 * File name: SwooleApp.php
 * Create date: 2019/10/11
 * Description: swoole项目启动文件
 *****************************************************/

namespace Lib;

use Lib\Base\CurrentRequest;
use Lib\Base\Request;
use Lib\Base\Response;
use Lib\Cache\RedisAction;
use Lib\Exception\OutputException;

class SwooleApp
{
    private $server;
    private $server_name;
    private $master_pid_file;
    private $master_pid_file_fd;

    /**
     *  init
     */
    private function init()
    {

        $this->server_name = AMS_SERVER_NAME;
        $this->master_pid_file = '/var/run/' . $this->server_name . '_master.pid';
    }

    /**
     * 启动
     */
    public function start()
    {
        tnm2Report(10272152, 1);

        $this->init();
        $this->master_pid_file_fd = fopen($this->master_pid_file, "c+");
        if (!flock($this->master_pid_file_fd, LOCK_EX | LOCK_NB)) { //表示程序已启动
            echo $this->get_master_pid() . '程序已启动';
            die;
        }

        $iWorkerNum = 8;
        if (IsTestMechine() || defined('ENV') && ENV == 'test') {
            $iWorkerNum = 1;
        }

        $this->server = new \Swoole\Http\Server(AMS_SERVER_HOST, AMS_SERVER_PORT);
        $this->server->set([
            'worker_num' => $iWorkerNum,
            'daemonize' => true,
            'log_file' => AMS_SERVER_LOG,
            "reload_async" => true,
            'http_gzip_level' => 0,
            'http_compression' => false
        ]);

        $this->server->on('Start', array($this, 'on_master_start'));
        $this->server->on('Request', array($this, 'on_request'));
        $this->server->on('WorkerStart', array($this, 'on_worker_start'));

        fputs(STDOUT, "\033[42mSwoole HTTP server start success !\033[0m" . PHP_EOL);
        $this->server->start();
    }

    /**
     * 停止
     *
     * @return bool
     */
    public function stop()
    {
        tnm2Report(10274494, 1);

        $this->init();
        $pid = $this->get_master_pid();
        if (!$pid) return false;

        if (posix_kill($pid, 15)) {
            return true;
        }

        unlink($this->master_pid_file);
        return false;
    }

    /**
     * master start
     */
    public function on_master_start()
    {
        $this->set_process_name($this->server_name . "_master");
        $this->gen_master_pid_file();
    }

    /**
     * gen_master_pid_file
     *
     * @return bool
     */
    private function gen_master_pid_file()
    {
        ftruncate($this->master_pid_file_fd, 0);
        fwrite($this->master_pid_file_fd, getmypid());
        fflush($this->master_pid_file_fd);
        return true;
    }

    /**
     * get_master_pid
     *
     * @return bool|string
     */
    private function get_master_pid()
    {
        if (file_exists($this->master_pid_file)) {
            $pid = file_get_contents($this->master_pid_file);
            return $pid;
        } else {
            return false;
        }
    }

    /**
     * set_process_name
     *
     * @param $name
     */
    private function set_process_name($name)
    {
        if (function_exists('cli_set_process_title')) {
            @cli_set_process_title($name);
        } else if (function_exists('swoole_set_process_name')) {
            @swoole_set_process_name($name);
        } else {
            trigger_error(__METHOD__ . " failed. require cli_set_process_title or swoole_set_process_name.");
        }
    }

    /**
     * work启动或者重启时执行
     *
     * @param $serv
     * @param $workerId
     */
    public function on_worker_start($serv, $workerId)
    {
        //1. 动态加载配置文件
        $config = APP_PATH . DIRECTORY_SEPARATOR . 'Config' . DIRECTORY_SEPARATOR  . 'swoole.inc.php';
        if (is_file($config)) {
            require_once $config;
        }
        require_once NTX_LIB . DIRECTORY_SEPARATOR . 'Config' . DIRECTORY_SEPARATOR . 'config.php';
        require_once NTX_LIB . DIRECTORY_SEPARATOR . 'Config' . DIRECTORY_SEPARATOR . 'global.php';
    }

    /**
     * 接受request请求
     *
     * @param $swooleRequest
     * @param $swooleResponse
     * @return bool
     */
    public function on_request($swooleRequest, $swooleResponse)
    {
        //屏蔽favicon.ico
        if ($swooleRequest->server['request_uri'] == '/favicon.ico') {
            return $swooleResponse->end("");
        }

        //初始化
        Request::init($swooleRequest);
        Response::init($swooleResponse);

        //开始时间
        Request::setGetParam('cgiStartTime', microtime(true));

        tnm2Report(10272153, 1);

        //统计请求量
        global $g_request_time_record, $g_paas_request_times, $g_paas_reject_times;

        if ($g_paas_reject_times === null) {
            $g_paas_reject_times = 0;
        }

        //防止雪崩
        $isReturn = false;
        $master_time = $swooleRequest->server['master_time']; //master接收到请求的时间
        $time = time();
        if (($time - $master_time) > 2) {
            RedisAction::destruct();
            Request::destruct();
            Response::destruct();
            //$slog = 'timeout:'.date('Y-m-d H:i:s',$time).'-- master_time:'. date('Y-m-d H:i:s',$master_time) .'--'.'['.__FILE__.']['.__LINE__.']['.LP_DEBUG.']['.$request->get['sAmsSerial'].']pid:'.getmypid()." fd:".$fd.' start request_time:'.$request->get['request_time'];
            //error_log($slog."\n",3,AMS_SERVER_LOG."_".date("YmdH").".txt");
            //增加告警，持续的话需要紧急扩容了，返回503
            $swooleResponse->status(503);
            $swooleResponse->end("503 Service Unavailable");
            tnm2Report(10272206, 1);
            $isReturn = true;
            $g_paas_reject_times = 1 + $g_paas_reject_times;
            //return;
        }

        $c = htmlspecialchars(strip_tags(Request::getParam("c")));
        $action = htmlspecialchars(strip_tags(Request::getParam("a")));
        $route = htmlspecialchars(strip_tags(Request::getParam("route")));

        //兼容route参数
        if ($route) {
            $routeArr = explode('/', $route);

            //获取ctrl 和 action
            $c = $routeArr[0];
            $action = $routeArr[1];
        }

        //记录请求量
        if ($c == "Action" && $action == "alive") {
            RedisAction::destruct();
            Request::destruct();
            Response::destruct();
            //监控的不做统计
            return $swooleResponse->end("ok");
        } else {
            $g_paas_request_times = 1 + $g_paas_request_times;

            if (time() % 2 == 0) {
                if ($g_request_time_record != time()) {
                    $g_request_time_record = time();

                    $g_paas_request_times_tmp = $g_paas_request_times;
                    $g_paas_reject_times_tmp = $g_paas_reject_times;

                    $g_paas_request_times = 0;
                    $g_paas_reject_times = 0;

                    error_log('[' . date('Y-m-d H:i:s') . ']visit:' . $g_paas_request_times_tmp . ",reject:" . $g_paas_reject_times_tmp . "\n", 3, LOG_PATH . "visit_" . date("Ymd") . ".txt");
                }
            }
        }

        if ($isReturn) {
            return false;
        }
        //统计请求量 END

        //测试环境，打印全部请求变量
        global $g_is_test_mechine;
        if (RUN_ENV == 'internal' && $g_is_test_mechine || defined('ENV') && ENV == 'test') {

            //get log
            error_log('[' . date('Y-m-d H:i:s') . ']HTTP GET Data:' . var_export(Request::getParam(), true) . "\n", 3, LOG_PATH . date("Ymd") . ".log");

            //post log
            error_log('[' . date('Y-m-d H:i:s') . ']HTTP POST Data:' . var_export(Request::postParam(), true) . "\n", 3, LOG_PATH . date("Ymd") . ".log");
            //cookie log
            error_log('[' . date('Y-m-d H:i:s') . ']HTTP COOKIE Data:' . var_export(Request::cookieParam(), true) . "\n", 3, LOG_PATH . date("Ymd") . ".log");
        }

        //上报项目的访问量
        if ($c != "Action" && is_numeric(AMS_SERVER_TNM)) {
            tnm2Report(AMS_SERVER_TNM, 1);
        }

        $ctrl = "Ctrl\\{$c}Ctrl";

        if (!$c) {
            tnm2Report(836967, 1);
            return  Response::swooleSystemOutputJson(-500, "no ctrl");
        }

        if (!$action) {
            tnm2Report(836968, 1);
            return  Response::swooleSystemOutputJson(-500, "no action");
        }

        //内部swoole环境
        if (RUN_ENV == 'internal' && isSwoole()) {
            try {
                //有写实例ID
                if (defined('PAAS_INSTANCE_ID') && !empty(PAAS_INSTANCE_ID)) {

                    $req = new CurrentRequest(Request::getRequest(), Response::getResponse());
                    $ams = new \Ams($req);

                    //openid
                    $ams->sUin = Request::param('iUin');

                    //设置实例ID
                    $ams->SetActionInfo(PAAS_INSTANCE_ID, S_SERVICE_TYPE);

                    //流水号
                    if (!Request::getParam('sAmsSerial')) {
                        Request::setParam('sAmsSerial', $ams->GetSerialNum());
                    }

                    //ams类
                    Request::setServerParam('ams', $ams);

                } else {
                    //没有实例ID的情况下，生成流水
                    if (!empty(Request::param('iAmsActivityId'))) {
                        $activityId = Request::param('iAmsActivityId');
                    } else {
                        $activityId = '281592';
                    }

                    if (defined('S_SERVICE_TYPE') && !empty(S_SERVICE_TYPE)) {
                        $sServiceType = S_SERVICE_TYPE;
                    } else if (!empty(Request::param('sServiceType'))) {
                        $sServiceType = Request::param('sServiceType');
                    } else {
                        $sServiceType = 'internal';
                    }
                    if (empty(Request::param('sAmsSerial'))) {
                        Request::setGetParam('sAmsSerial', createAmsSerial($activityId, strtoupper($sServiceType)));
                    }
                }

                //日志类
                $obj = new \Logger(Request::getRequest());
                Request::setServerParam('logObj', $obj);

                //销毁
                $ams = $obj =  null;
                unset($ams, $obj);
            } catch (\Exception $e) {
                return Response::swooleSystemOutputJson('-100005', $e->getMessage());
            } catch (\Error $e) {
                return  Response::swooleSystemOutputJson('-100006', $e->getMessage());
            }
        }

        //释放内存
        unset($swooleRequest, $swooleResponse);

        try {

            //ulink 流程初始化
            if (RUN_ENV == 'ulink') {
                $status = UlinkMain::init("{$c}Ctrl" . '/' . $action);
                if (!$status) {
                    return false;
                }
            }

            //请求路由到对应的模块
            $reflectionObj = new \ReflectionClass($ctrl);
            $moduleObj = $reflectionObj->newInstance();
            $reflectionMethod = new \ReflectionMethod($ctrl, $action);
            //公开方法
            if ($reflectionMethod->isPublic() || $reflectionMethod->isStatic()) {
                $reflectionMethod->invoke($moduleObj, 0);
                RedisAction::destruct();
                Request::destruct();
                Response::destruct();
            } else {
                throw new \Exception("$ctrl:$action 是private方法，对外无法调用，请修改！", -1001);
            }
        } catch (OutputException $e) {
            //output 不做任何处理
            RedisAction::destruct();
            Request::destruct();
            Response::destruct();
        } catch (\Exception $e) {

            $message = [
                "file" => $e->getFile(),
                "line" => $e->getLine(),
                "message" => $e->getMessage(),
            ];
            recordLog('出现异常请检查! ' . var_export($message, true), __FILE__, __LINE__, LP_ERROR);

            //测试环境告知具体错误
            $data = [];
            if ($g_is_test_mechine || defined('ENV') && ENV == 'test') {
                $data = $message;
            }
            return  Response::swooleSystemOutputJson('-100007', '出现异常请检查!', $data);
        } catch (\Error $e) {
            $message = [
                "file" => $e->getFile(),
                "line" => $e->getLine(),
                "message" => $e->getMessage(),
            ];
            recordLog('出现语法错误请检查 ' . var_export($message, true), __FILE__, __LINE__, LP_ERROR);
            //测试环境告知具体错误
            $data = [];
            if ($g_is_test_mechine || defined('ENV') && ENV == 'test') {
                $data = $message;
            }

            return Response::swooleSystemOutputJson('-100008', '出现语法错误请检查', $data);
        }
    }
}
